package com.generic.mybatis.dal.interceptor;

import com.generic.mybatis.dal.dao.GenericDao;
import com.generic.mybatis.dal.interceptor.automap.SqlBuilderFactory;
import com.generic.mybatis.dal.util.Reflections;
import com.google.common.base.Joiner;
import com.google.common.collect.Sets;
import org.apache.ibatis.builder.SqlSourceBuilder;
import org.apache.ibatis.executor.Executor;
import org.apache.ibatis.mapping.MappedStatement;
import org.apache.ibatis.mapping.SqlSource;
import org.apache.ibatis.plugin.*;
import org.apache.ibatis.session.Configuration;
import org.apache.ibatis.session.ResultHandler;
import org.apache.ibatis.session.RowBounds;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.stereotype.Component;

import java.lang.reflect.Method;
import java.util.Map;
import java.util.Properties;
import java.util.Set;

/**
 * 根据参数parameterObject配置的注解信息，自动生成sql语句
 *
 * @author wuaj
 */
@Intercepts({
        @Signature(
                type = Executor.class,
                method = "query",
                args = {MappedStatement.class, Object.class, RowBounds.class, ResultHandler.class})
        , @Signature(
        type = Executor.class,
        method = "update",
        args = {MappedStatement.class, Object.class})
})
@Component
public class AutoMapperInterceptor implements Interceptor {

    private static final Logger LOGGER = LoggerFactory.getLogger(AutoMapperInterceptor.class);

    private static final Set<String> baseDaoMethodSet = Sets.newHashSet();

    private static final SqlBuilderFactory DEFAULT_SQL_BUILDER_FACTORY = new SqlBuilderFactoryDefault();

    static {
        Method[] methods = GenericDao.class.getDeclaredMethods();
        for (Method method : methods) {
            baseDaoMethodSet.add(method.getName());
        }
    }

    @Override
    public Object intercept(Invocation invocation) throws Throwable {
        Object[] queryArgs = invocation.getArgs();
        final MappedStatement ms = (MappedStatement) queryArgs[0];
        final Object parameterObject = queryArgs[1];
        final String id = ms.getId();
        String daoMethod = id.substring(id.lastIndexOf('.') + 1).trim();
        Class daoClass = Class.forName(id.substring(0, id.lastIndexOf('.')));

        // 判断方法名是否已定义的方法名 且 判断Dao类型是否GenericDao的子类
        if (baseDaoMethodSet.contains(daoMethod) && GenericDao.class.isAssignableFrom(daoClass)) {
            Class dtoClass = daoClass.isInterface() ? Reflections.getInterfaceGenericType(daoClass, 0) :
                    Reflections.getClassGenericType(daoClass);

            // 根据方法名、DTO、参数 构造生成sql (由builder构造生成)
            String newSql;
            // FIX BUG ms线程不安全
            synchronized (ms) {
                newSql = DEFAULT_SQL_BUILDER_FACTORY.getBuilder(daoMethod).buildSql(ms, dtoClass, parameterObject);
            }

            if (LOGGER.isDebugEnabled()) {
                LOGGER.debug("Auto generated sql:{}", newSql);
                if (parameterObject instanceof Map) {
                    LOGGER.debug("Parameter: {}", Joiner.on(" , ").withKeyValueSeparator(" = ").join((Map) parameterObject));
                }
            }

            // 生成新的SqlSource、替换MyBatis的SqlSource
            SqlSource sqlSource = buildSqlSource(ms.getConfiguration(), newSql,
                    parameterObject == null ? null : parameterObject.getClass());
            // 通过反射修改 MappedStatement 的 sqlSource变量,从而修改sql文及参数
            Reflections.setFieldValue(ms, "sqlSource", sqlSource);
        }

        queryArgs[0] = ms;
        // 传递给下一个拦截器处理
        return invocation.proceed();
    }

    @Override
    public Object plugin(Object target) {
        return target instanceof Executor ? Plugin.wrap(target, this) : target;
    }

    @Override
    public void setProperties(Properties properties) {
        // Do nothing because properties not to modify
    }

    private SqlSource buildSqlSource(Configuration configuration, String originalSql, Class<?> parameterType) {
        return new SqlSourceBuilder(configuration).parse(originalSql, parameterType, null);
    }
}
